#import "MapViewController.h"

// Whether there is a currently active internet connection
static BOOL hasConnection;

/**
 * This class represents the main view of the application. 
 *
 * It is responsible for displaying the map view and all its elements. Such 
 * elements include: annotations, callout bubbles, and current user location.
 *
 * Event, building, and pin note events are handled within this class.
 * 
 * @author      Justin Hollander
 * @author      James Lin
 * @version     1.4.4
 * @since       0.1.0
 * @see         MKMapViewDelegate
 * @see         MapBuildingSearchDelegate
 * @see         SFUEventSearchResponseDelegate
 * @see         PinNoteManagerDelegate
 * @see         MapPOISearchDelegate
 */
@implementation MapViewController

@synthesize mapView;
@synthesize mapTabBar;
@synthesize searchItem;
@synthesize settingsItem;
@synthesize pinNotesItem;
@synthesize bookmarksItem;
@synthesize fourSquareItem;
@synthesize isBuildingSearch;

/**
 * Initialize the view controller's subviews.
 */
- (void)initWindowObjects {
	// Create the map view
	mapView = [[MKMapView alloc] initWithFrame:CGRectMake(ZERO, ZERO, 
														  VIEW_WIDTH, MAP_VIEW_HEIGHT)];
	mapView.scrollEnabled = YES;
	mapView.zoomEnabled = YES;
	mapView.mapType = [MapAppSettings sharedSettings].currentMapType;
	
	// Create the tab bar items
	self.mapTabBar = [[UITabBar alloc] initWithFrame:CGRectMake(ZERO, MAP_VIEW_HEIGHT, 
																VIEW_WIDTH, TAB_BAR_WIDTH)];
	
	settingsItem = [[UITabBarItem alloc] initWithTitle:@"Settings" 
												 image:[UIImage imageNamed:@"settings_icon.png"] 
												   tag:0];
	bookmarksItem = [[UITabBarItem alloc] initWithTitle:@"My Events" 
												  image:[UIImage imageNamed:@"bookmarks_icon.png"]
													tag:1];
	fourSquareItem = [[UITabBarItem alloc] initWithTitle:@"Foursquare" 
												  image:[UIImage imageNamed:@"foursquare_icon.png"]
													tag:2];
	pinNotesItem = [[UITabBarItem alloc] initWithTitle:@"My Pins" 
												 image:[UIImage imageNamed:@"pins_icon.png"]
												   tag:3];
	searchItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemSearch
															tag:4];
	
	// Add the tab bar items to the tab bar
	[mapTabBar setItems:[NSArray arrayWithObjects:settingsItem, bookmarksItem, 
						 fourSquareItem, pinNotesItem, searchItem, nil] 
			   animated:YES];
}

/**
 * Converts our userdefined GPSLocation into a MKMapView supported point location type
 *
 * @param   gps                     userdefined type encapsulating the coordinates of a location 
 * @return  CLLocationCoordinate2D  the MKMapView supported type for a point location
 */
- (CLLocationCoordinate2D)addressLocation:(GPSLocation*)gps {
	CLLocationCoordinate2D location;
	location.latitude = gps.latitude;
	location.longitude = gps.longitude;
	
	return location;
}

/**
 * Helper method to check whether there is an overlapping annotation 
 * at a given annotation.
 *
 * @return  the overlapping annotation if there is one
 */
- (id<MKAnnotation>)overlappingAnnotation:(id<MKAnnotation>)annotation {
	for (id<MKAnnotation> ann in self.mapView.annotations) {
		if (ann != self.mapView.userLocation && 
			ann.title == annotation.title &&
			ann.coordinate.latitude == annotation.coordinate.latitude && 
			ann.coordinate.longitude == annotation.coordinate.longitude) {
			return ann;
		}
	}
	return nil;
}

/**
 * Helper method written by Jim, referenced at:
 * http://codisllc.com/blog/zoom-mkmapview-to-fit-annotations/
 *
 * Copyright 2010 Codis, LLC. All Rights Reserved.
 */
- (void)zoomToFitMapAnnotations {
    if ([mapView.annotations count] == 0) {
        return;
	}
	
	if ([mapView.annotations count] == 1) {
		for (id<MKAnnotation> ann in self.mapView.annotations) {
			if (ann == self.mapView.userLocation) {
				return;
			}
		}
	}
    
    CLLocationCoordinate2D topLeftCoord;
    topLeftCoord.latitude = -90;
    topLeftCoord.longitude = 180;
    
    CLLocationCoordinate2D bottomRightCoord;
    bottomRightCoord.latitude = 90;
    bottomRightCoord.longitude = -180;
    
    for (AddressAnnotation* annotation in mapView.annotations) {
		topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude);
		topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude);
		
		bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude);
		bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude);
    }
    
    MKCoordinateRegion region;
    region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5;
    region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1; // Add a little extra space on the sides
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; // Add a little extra space on the sides
    
    region = [mapView regionThatFits:region];
    [mapView setRegion:region animated:YES];
}

/**
 * Adjust the map region to fit only the Foursquare annotations.
 *
 * Modified helper method written by Jim, referenced at:
 * http://codisllc.com/blog/zoom-mkmapview-to-fit-annotations/
 *
 * Copyright 2010 Codis, LLC. All Rights Reserved.
 */
- (void)zoomToFitFoursquareAnnotations {
    if ([mapView.annotations count] == 0) {
        return;
	}
	
	if ([mapView.annotations count] == 1) {
		for (id<MKAnnotation> ann in self.mapView.annotations) {
			if (ann == self.mapView.userLocation) {
				return;
			}
		}
	}
    
    CLLocationCoordinate2D topLeftCoord;
    topLeftCoord.latitude = -90;
    topLeftCoord.longitude = 180;
    
    CLLocationCoordinate2D bottomRightCoord;
    bottomRightCoord.latitude = 90;
    bottomRightCoord.longitude = -180;
    
    for (AddressAnnotation* annotation in mapView.annotations) {
		if ([annotation isKindOfClass:[AddressAnnotation class]] && 
			(annotation.annType == AnnotationTypeFoursquareTip || 
			 annotation.annType == AnnotationTypeFoursquareVenue ||
			 annotation.annType == AnnotationTypeFoursquareFriend)) {
				topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude);
				topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude);
				
				bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude);
				bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude);
			}
	}
    
    MKCoordinateRegion region;
    region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5;
    region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;
    region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1; // Add a little extra space on the sides
    region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1; // Add a little extra space on the sides
    
    region = [mapView regionThatFits:region];
    [mapView setRegion:region animated:YES];
}

/**
 * Helper method to check for accelerometer reminder necessity.
 */
- (void)checkMapClutter {
	// Accelerometer reminder
	if ([self.mapView.annotations count] > 10 && reminderTimerCount < 4) {
		reminderTimerCount++;
		
		if (reminderTimerCount == 4) {
			[GraphicsUtilities showPrompt:@"Shake the iPhone to clear all pins." 
								   inView:self.view 
								withColor:nil 
								andHeight:40];
			reminderTimerCount = 0;
			[self performSelector:@selector(dismissReminder) 
					   withObject:nil
					   afterDelay:5];
		}
	}
}

/**
 * Displays a building on the map.
 *
 * @param building  the building to be shown on the map
 */
- (void)showAddress:(Building*)building {
	MKCoordinateRegion region;
	MKCoordinateSpan span;
	
	span.latitudeDelta = 0.005;
	span.longitudeDelta = 0.005;
	
	CLLocationCoordinate2D location = [self addressLocation:building.loc];
	
	region.span = span;
	region.center = location;
	
	NSString* subtitle = (self.isBuildingSearch) ? @"Touch to view building info" : @"Touch to view events";
	
	// Create the annotation for the building
	addAnnotation = [AddressAnnotation mapAnnotationWithCoordinate:location 
														  andTitle:building.name 
													   andSubtitle:subtitle 
													   andType:AnnotationTypeBuilding];
	
	// Check for no duplicate first
	id<MKAnnotation> ann = [self overlappingAnnotation:addAnnotation];
	
	if (ann) {
		addAnnotation = (AddressAnnotation*)ann;
		[mapView removeAnnotation:addAnnotation];
	}
	
	// Display the building annotation on the map
	[mapView addAnnotation:addAnnotation];
	
	// Adjust map view to center on the building annotation
	[mapView setRegion:region animated:TRUE];
	[mapView regionThatFits:region];
}

/**
 * Displays a point of interest on the map.
 *
 * @param poi  the point of interest to be shown on the map
 */
- (void)showPointOfInterest:(PointOfInterest*)poi {
	MKCoordinateRegion region;
	MKCoordinateSpan span;
	
	span.latitudeDelta=0.005;
	span.longitudeDelta=0.005;
	
	CLLocationCoordinate2D location = [self addressLocation:poi.loc];
	
	region.span=span;
	region.center=location;
	
	// Create the annotation for the point of interest
	addAnnotation = [POIAddressAnnotation mapAnnotationWithCoordinate:location 
															 andTitle:poi.name 
														  andSubtitle:[SFUConstants POICategoryString:poi.category] 
															  andType:AnnotationTypePOI 
															   andPOI:poi];
	
	// Display the point of interest annotation on the map
	[mapView addAnnotation:addAnnotation];
}

/**
 * Displays a pin note on the map.
 *
 * @param annotation  the pin note to be shown on the map
 */
- (void)showSavedAnnotation:(AddressAnnotation*)annotation {
	// Display the pin note on the map
	[mapView addAnnotation:annotation];
}

/**
 * Wrapper method to display a building on the map
 *
 * @param buildingToDisplay Reference to the building that will be displayed
 */
- (void)displayBuilding:(Building*)buildingToDisplay {
	[self showAddress:buildingToDisplay];
}

/**
 * Removes all active annotations from the map except for the user location.
 */
- (void)removeActiveAnnotations {
	NSArray* buildingList = [[BuildingCollection sharedBuildingCollection] getDataTypes];
	NSMutableArray *toRemove = [NSMutableArray array];
	
	// Find all the annotations to remove
	for (id annotation in self.mapView.annotations) {
		if (annotation != self.mapView.userLocation) {
			[toRemove addObject:annotation];
		}
	}
	
	[self.mapView removeAnnotations:toRemove];
	
	// Free the annotations
	for (id annotation in toRemove) {
		[annotation release];
	}
	
	// Free temporary building event lists
	for (Building* b in buildingList) { 
		if (b.eventList != nil) {
			[b.eventList removeAllObjects];
			[b.eventList release], b.eventList = nil;
		}
	}
}

/**
 * Process a building search request.
 * 
 * @param building  the building to be displayed
 */
- (void)searchRequestByBuilding:(Building*)building isNew:(BOOL)isNew {
	self.isBuildingSearch = YES;
	
	//if (isNew) {
	//	[self removeActiveAnnotations];
	//}
	
	if (building != nil) {
		[self displayBuilding:building];
	}
}

/**
 * Assign each matchded event to its building location.
 *
 * The event location is matched with the name of the building.
 * Each matching event is then added to a building's event listing.
 * 
 * @param matchedEventsWithLocations  event matches from event search
 */
- (void)mapMatchedEventsByBuilding:(NSArray*)matchedEventsWithLocations {
	BuildingCollection* bc = [BuildingCollection sharedBuildingCollection];
	NSArray* buildingList = [bc getDataTypes];
	
	for (SFUEvent* matchedEvent in matchedEventsWithLocations) {
		for (Building* building in buildingList) {
			if ([building.name isEqualToString:matchedEvent.eventLocation]) {
				if(building.eventList == nil) {
					building.eventList = [[NSMutableArray alloc] init];
				}
				
				[building.eventList addObject:matchedEvent];
				break;
			}
		}
	}
}

/**
 * Handle a point of interest search request.
 *
 * @param pois  the points of interest of which a search is requested
 * @see         MapPOISearchDelegate
 */
- (void)searchRequestByPOIs:(NSArray*)pois{
	PointOfInterestCollection* poic = [PointOfInterestCollection sharedPointOfInterestCollection];
	[self removeActiveAnnotations];
	for (NSString* poiCategory in pois) {
		for (PointOfInterest* poi in [poic getDataTypes]) {
			if ([SFUConstants POICategoryString:poi.category] == poiCategory) {
				[self showPointOfInterest:poi];
			}
		}
	}
	
	[self setDefaultMapRegion];
}

/**
 * Handles the set of event matches generated by an event search.
 * 
 * @param eventMatches  array of matching events
 * @see                 SFUEventSearchResponseDelegate
 */
- (void)eventResponseBySearchSuccess:(NSArray*)eventMatches {
	NSArray* buildingList = [[BuildingCollection sharedBuildingCollection] getDataTypes];
	
	[self removeActiveAnnotations];
	self.isBuildingSearch = NO;
	
	if (eventMatches != nil) {
		// Check if search yielded results
		if ([eventMatches count] == 0) {
			[GraphicsUtilities displayAlertWithTitle:@"No matching events found." 
											 message:nil];
			return;
		}
		else {
			BOOL allNull = YES;
			
			for (SFUEvent* event in eventMatches) {
				if (event.eventLocation != nil) {
					allNull = NO;
					break;
				}
			}
			
			if (allNull) {
				[GraphicsUtilities displayAlertWithTitle:@"No matching events found." 
												 message:nil];
				return;
			}
		}
		
		// Assign each event to its location
		[self mapMatchedEventsByBuilding:eventMatches]; 
		
		// Display the events
		for (Building* building in buildingList) {
			if ([building.eventList count] > 0) {
				[self displayBuilding:building];
			}
		}
		
		[self setDefaultMapRegion];
	}
}

/**
 * Handles the set of event matches generated by an event search.
 * 
 * @param msg  reason for failure
 * @see        SFUEventSearchResponseDelegate
 */
-(void) eventResponseBySearchFailure:(NSString*) msg{
	UIAlertView* alertView = [[[UIAlertView alloc] initWithTitle:@"Error" 
														 message:msg
														delegate:self 
											   cancelButtonTitle:@"Ok" 
											   otherButtonTitles:nil] autorelease];
	[alertView show];
}

/**
 * Handles incoming tab bar events and delegates the task.
 *
 * @param tabBar  the reference to the tab bar
 * @param item    the selected tab bar item
 */
- (void)tabBar:(UITabBar*)tabBar didSelectItem:(UITabBarItem*)item {
	switch (item.tag) {
		case 0:
			[self performSelector:@selector(handleSettings)];
			break;
		case 1:
			[self performSelector:@selector(handleBookmarks)];
			break;
		case 2:
			if ([self hasActiveConnection]) {
				[self performSelector:@selector(handleFourSquare)];
			} else {
				[GraphicsUtilities displayAlertWithTitle:NO_CONNECTIVITY_MSG
												 message:@"Foursquare is currently unavailable."];
			}
			break;
		case 3:
			[self performSelector:@selector(handleUserPins)];
			break;
		case 4:
			[self performSelector:@selector(handleSearch)];
			break;
		default:
			break;
	}
}

/**
 * Helper method to add subviews to this view controller.
 */
- (void)addViews {
	[self.view addSubview:mapView];
	[self.view addSubview:mapTabBar];
}

/**
 * Helper method to register the view controller as a delegate to several modules.
 */
- (void)registerDelegates {
	[mapTabBar setDelegate:self];
	[mapView setDelegate:self];
	
	[[EventManager sharedEventManager] setReachabilityDelegate:self];
	[[EventManager sharedEventManager] setViewBuildingDelegate:self];
	
	BusDirectoryManager* bdm = [BusDirectoryManager sharedBusDirectoryManager];
	[bdm setReachabilityDelegate:self];
	[bdm setDirectoryDelegate:[MapAppSettingsView sharedSettingsView]];
}

/**
 * Activates the settings view upon touching the settings tab item.
 */
- (void)handleSettings {
	MapAppSettingsView* masv = [MapAppSettingsView sharedSettingsView];
	[masv setMVC:self];
	[masv displayWithNavController:self.navigationController];
}

/**
 * Activates the note pin tools upon touching the my pins tab item.
 */
- (void)handleUserPins {
	[[PinNoteManager sharedPinNoteManager] activateWithDelegate:self];
}

/**
 * Activates the search list viewer upon touching the search tab item.
 */
- (void)handleSearch {
	MapSearchListViewer* mslv = [MapSearchListViewer sharedMapSearchListViewer];
	[mslv displayWithNavController:self.navigationController 
				bldgSearchDelegate:self 
				 poiSearchDelegate:self
			   eventSearchDelegate:self];
}

/**
 * Activates the bookmark view upon touching the bookmarks tab item.
 */
- (void)handleBookmarks {
	EventManager* em = [EventManager sharedEventManager];
	self.isBuildingSearch = NO;
	
	if (![em displayBookmarkedWithNavController:[self navigationController]]) {
		mapTabBar.selectedItem = nil;
	}
}

/**
 * Activates the Foursquare manager upon touching the Foursquare tab item.
 */
- (void)handleFourSquare {
	[[FoursquareManager sharedFoursquareManager] displayFromTabBar:mapTabBar 
													  withDelegate:self];
}

/**
 * Get the current location of the user.
 *
 * @return  the current location of the user
 * @see     FoursquareManagerDelegate
 */
- (CLLocation*)getCurrentUserLocation {
	return mapView.userLocation.location;
}

/**
 * Handle a notification that the integrated Foursquare tools are done
 * their use.
 *
 * @see  FoursquareManagerDelegate
 */
- (void)handleFoursquareToolsDone {
	mapTabBar.selectedItem = nil;
}

/**
 * Calculate the GPS coordinate given a point relative to the map view.
 *
 * @param point  the point to convert into a coordinate
 * @return       the calculated coordinate
 * @see          FoursquareManagerDelegate
 */
- (CLLocationCoordinate2D)coordFromPoint:(CGPoint)point {
	return [mapView convertPoint:point toCoordinateFromView:mapView];
}

/**
 * Handle a request to view the given Foursquare annotations on the map.
 *
 * @param annotations  the Foursquare annotations to be placed on the map
 * @see                FoursquareManagerDelegate
 */
- (void)displayFoursquareAnnotations:(NSArray*)annotations {
	[self removeActiveAnnotations];
	
	for (AddressAnnotation* ann in annotations) {
		//NSLog(@"placing %@ at %f %f", 
		//	  venue.title, venue.coordinate.latitude, venue.coordinate.longitude);
		[self showSavedAnnotation:ann];
	}
	
	[self zoomToFitFoursquareAnnotations];
	//[self zoomToFitMapAnnotations];
}

/**
 * Initialize the MapViewController.
 * 
 * @return  the initialized MapViewController
 */
- (id)init {
	if (self = [super init]) {
		UIAccelerometer *accel = [UIAccelerometer sharedAccelerometer];
		[self initWindowObjects];
		[self registerDelegates];
		[self addViews];
		
		accel.delegate = self;
		accel.updateInterval = 1.0f/60.0f;
		
		// Make the building collection data file
		[BuildingDataUtility buildBuildingData];
		// Make the point of interest collection data file
		[PointOfInterestDataUtility buildPOICollection];
		
		queue = [NSOperationQueue new];
		[queue setMaxConcurrentOperationCount:MAX_CONCURRENT_OPERATION];
		reminderTimerCount = 0;
	}
	return self;
}

/**
 * Displays building information of the given building name.
 */
- (void)showDetailsOfBuilding:(NSString*)name {
	// Find the matching building
	Building* foundBuilding = [ParsingUtilities parseBuildingFromString:name];
	if (foundBuilding) {
		BuildingViewer* bv = [BuildingViewer sharedBuildingViewer];
		[bv displayBuilding:foundBuilding 
			  navController:self.navigationController 
				andDelegate:self];
		
		// Display the events in the building
		//[[EventManager sharedEventManager] displayEvents:[foundBuilding eventList]
		//								   navController:[self navigationController]];
	}
}

/**
 * Displays details of the given pin note.
 */
- (void)showDetailsOfPinNote:(AddressAnnotation*)pinNote {
	[[PinNoteManager sharedPinNoteManager] displayPinNote:pinNote delegate:self];
}

/**
 * Helper method to handle the call to an annotation's detail disclosure button.
 */
- (void)callAnnotationDisclosure {
	NSArray* annotations = [self.mapView selectedAnnotations];
	id<MKAnnotation> annotation = nil;
	
	// Find the selected annotation
	if (annotations != nil) {
		for (id<MKAnnotation> ann in annotations) {
			annotation = ann;
		}
	}
	
	// Delegate annotation disclosure call based on annotation type
	if (annotation) {
		AddressAnnotation* addrAnn = (AddressAnnotation*)annotation;
		if (addrAnn.annType == AnnotationTypeBuilding) {
			//NSLog(@"Building annotation");
			[self showDetailsOfBuilding:addrAnn.title];
		} else if (addrAnn.annType == AnnotationTypePOI) {
			// Do nothing
		} else if (addrAnn.annType == AnnotationTypePinNote) {
			//NSLog(@"Pin note annotation");
			[self showDetailsOfPinNote:addrAnn];
		} else if (addrAnn.annType == AnnotationTypeFoursquareVenue) {
			//NSLog(@"Foursquare venue annotation");
			FoursquareVenueAnnotation* fsAnn = (FoursquareVenueAnnotation*)addrAnn;
			FoursquareManager* fsm = [FoursquareManager sharedFoursquareManager];
			[fsm requestCheckInAtVenue:fsAnn.venueInfo];
		} else if (addrAnn.annType == AnnotationTypeFoursquareSpecial) {
			[GraphicsUtilities displayAlertWithTitle:addrAnn.subtitle 
											 message:addrAnn.title];
		} else if (addrAnn.annType == AnnotationTypeFoursquareTip) {
			//NSLog(@"Foursquare tip annotation");
			FoursquareTipAnnotation* fsAnn = (FoursquareTipAnnotation*)addrAnn;
			NSString* title = [NSString stringWithFormat:@"%@ %@ at %@ says:",
							   fsAnn.tipInfo.tipUserFirstName,
							   fsAnn.tipInfo.tipUserLastName,
							   fsAnn.tipInfo.tipVenueName];
			[GraphicsUtilities displayAlertWithTitle:title 
											 message:fsAnn.tipInfo.tipText];
		} else if (addrAnn.annType == AnnotationTypeFoursquareFriend) {
			FoursquareFriendAnnotation* fsAnn = (FoursquareFriendAnnotation*)addrAnn;
			NSString* title = [NSString stringWithFormat:@"%@ says:",
							   fsAnn.title];
			NSString* msg = [NSString stringWithFormat:@"%@\n%@",
							 fsAnn.friendShout, 
							 fsAnn.friendCheckInDate];
			[GraphicsUtilities displayAlertWithTitle:title 
											 message:msg];
		}
	}
}

/**
 * Remove all active annotations on the map when device shaken.
 *
 * Accelerometer feature implementation.
 */
- (void)accelerometer:(UIAccelerometer *)acel didAccelerate:(UIAcceleration *)aceler {
	if ( sqrt(aceler.x * aceler.x + 
			  aceler.y * aceler.y + 
			  aceler.z * aceler.z) > 3.2f ){
		self.mapView.showsUserLocation = NO;
		[self removeActiveAnnotations];
		self.mapView.showsUserLocation = YES;
	}
}

/**
 * Reset the persistent pin note collection.
 */
- (void)resetPinNotes {
	NSMutableArray *toRemove = [NSMutableArray array];
	for (id ann in self.mapView.annotations) {
		if ([ann isKindOfClass:[AddressAnnotation class]]) {
			[toRemove addObject:ann];
		}
	}
	[self.mapView removeAnnotations:toRemove];
	[[AddressAnnotationCollection sharedAddressAnnotationCollection] clear];
}

/**
 * Begin caching new event data.
 */
- (void)runEventCaching {
	[[EventManager sharedEventManager] beginCachingEventsAtInterval:[MapAppSettings sharedSettings].eventCacheInterval];

	// Precache venue data
	[[FoursquareManager sharedFoursquareManager] silentUpdateWithDelegate:self];
}

/**
 * Everytime this view controller's view loads, ensure that its 
 * navigation bar is hidden in an animated fashion for smoothness.
 *
 * Also update the badge value on the bookmarks tabs and reset the
 * state of the tab bar.
 */
- (void)viewWillAppear:(BOOL)animated {
	self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
	[[self navigationController] setNavigationBarHidden:YES animated:YES];
	
	mapTabBar.selectedItem = nil;
	
	NSUInteger bv = [[[EventManager sharedEventManager] getBookmarkedEvents] count];
	[bookmarksItem setBadgeValue:[NSString stringWithFormat:@"%d", bv]];
	
	[super viewWillAppear:animated];
}

/**
 * Reset the navigation bar whenever this view disappears.
 */
- (void)viewWillDisappear:(BOOL)animated {
	self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
	[[self navigationController] setNavigationBarHidden:NO animated:YES];
	
	[self.navigationItem setLeftBarButtonItem:nil];
	[self.navigationItem setRightBarButtonItem:nil];
	
	[super viewWillDisappear:animated];
}

/**
 * Animate the map view to the default map region.
 */
- (void)setDefaultMapRegion {
	MKCoordinateRegion region;
	MKCoordinateSpan span;
	CLLocationCoordinate2D location;
	
	
	switch ([MapAppSettings sharedSettings].campus) {
		case 0:
			location.latitude = DEFAULT_LATITUDE;
			location.longitude = DEFAULT_LONGITUDE;
			span.latitudeDelta = DEFAULT_SPAN;
			span.longitudeDelta = DEFAULT_SPAN;
			
			break;
		case 1:
			location.latitude = SURREY_LATITUDE;
			location.longitude = SURREY_LONGITUDE;
			span.latitudeDelta = SURREY_SPAN;
			span.longitudeDelta = SURREY_SPAN;
			break;
		case 2:
			location.latitude = VANCOUVER_LATITUDE;
			location.longitude = VANCOUVER_LONGITUDE;
			span.latitudeDelta = VANCOUVER_SPAN;
			span.longitudeDelta = VANCOUVER_SPAN;
			break;
		default:
			location.latitude = DEFAULT_LATITUDE;
			location.longitude = DEFAULT_LONGITUDE;
			span.latitudeDelta = DEFAULT_SPAN;
			span.longitudeDelta = DEFAULT_SPAN;	
			break;
	}
	
	region.span = span;
	region.center = location;
	
	// Set the map view region
	[mapView setRegion:region animated:TRUE];
	[mapView regionThatFits:region];
}

/**
 * Set up the view upon load.
 */
- (void)viewDidLoad {
	[super viewDidLoad];
	[self setDefaultMapRegion];
	[self.navigationController.navigationBar setHidden:YES];
	
	 // Enable current user location tracking
	self.mapView.showsUserLocation = YES;
	
	// Create a new empty cache for pin notes
	pinNotes = [[NSMutableArray array] retain];
	
	// Trigger event caching 5 seconds after the view loads
	[NSTimer scheduledTimerWithTimeInterval:5 
									 target:self 
								   selector:@selector(runEventCaching) 
								   userInfo:nil 
									repeats:NO];
	
	// Trigger map pin clutter checks every 15 seconds after the view loads
	reminderTimer = [NSTimer scheduledTimerWithTimeInterval:15 
													 target:self 
												   selector:@selector(checkMapClutter) 
												   userInfo:nil 
													repeats:YES];
	
	// Do an initial reachability check
	if (![self hasActiveConnection]) {
		[GraphicsUtilities displayAlertWithTitle:NO_CONNECTIVITY_MSG 
										 message:@"Application functionality may be limited."];
	}
}

/**
 * Helper method to silently check for reachability status.
 */
- (void)checkReachabilitySilent {
	Reachability* reachabilityCheck = [Reachability reachabilityForInternetConnection];
	NetworkStatus networkSts = [reachabilityCheck currentReachabilityStatus];
	
	hasConnection = (networkSts == NotReachable) ? NO : YES;
}

/**
 * Determine whether there is a current internet connection.
 * 
 * @return  whether there is a currently active internet connection
 * @see     ReachabilityDelegate
 */
- (BOOL)hasActiveConnection {
	[self checkReachabilitySilent];
	
	return hasConnection;
}

/**
 * Set the annotation and annotation view accordingly based on the annotation type.
 * 
 * @see  MKMapViewDelegate
 */
- (MKAnnotationView *)mapView:(MKMapView *)mapView 
			viewForAnnotation:(id<MKAnnotation>)annotation{
	MKPinAnnotationView *pinView = nil;	
	
	if (annotation != self.mapView.userLocation) {
		pinView = (MKPinAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:@"currentloc"];
		
		if (nil == pinView) {
			pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation
													   reuseIdentifier:@"currentloc"] autorelease];
		}
		
		// Clear pin image
		for (UIView* view in pinView.subviews) {
			[view removeFromSuperview];
		}
		
		// Set associated pin graphic
		AddressAnnotation* ann = (AddressAnnotation*)annotation;
		pinView.enabled = YES;
		
		if (ann.annType == AnnotationTypeBuilding) {
			[pinView setPinColor:MKPinAnnotationColorRed];
		} else if (ann.annType == AnnotationTypePOI) {
			UIImage* pinImage;
			
			if (pinImage = [SFUConstants POICategoryImage:ann.subtitle]) {
				UIImageView* imageView = [[[UIImageView alloc] initWithImage:pinImage] autorelease];
				
				[pinView addSubview:imageView];
			} else {
				pinImage = [UIImage imageNamed:@"teal_pin.png"];
				UIImageView* imageView = [[[UIImageView alloc] initWithImage:pinImage] autorelease];
				
				[pinView addSubview:imageView];
			}
		} else if (ann.annType == AnnotationTypePinNote || 
				   ann.annType == AnnotationTypePinNoteTemporary || 
				   ann.annType == AnnotationTypePinNoteSilent) {
			UIImage* pinImage = [UIImage imageNamed:@"purple_pin.png"];
			UIImageView* imageView = [[[UIImageView alloc] initWithImage:pinImage] autorelease];
			
			[pinView setPinColor:MKPinAnnotationColorPurple];
			[pinView addSubview:imageView];
		} else if (ann.annType == AnnotationTypeFoursquareVenue) {
			UIImage* pinImage = [UIImage imageNamed:@"none.png"];
			UIImageView* imageView = [[[UIImageView alloc] initWithImage:pinImage] autorelease];
			CGPoint c = imageView.center;
			c.x = c.x - 9;
			c.y = c.y - 10;
			imageView.center = c;
			[pinView addSubview:imageView];
			pinView.calloutOffset = CGPointMake(-8, -10);
			
			FoursquareVenue* fsv = ((FoursquareVenueAnnotation*)ann).venueInfo;
			
			if ([fsv.venueImageURL length] > 0) {
				SEL mySel = @selector(loadVenueImage:forView:);
				NSMethodSignature* signature = [[self class] instanceMethodSignatureForSelector:mySel];
				NSInvocation* inv = [NSInvocation invocationWithMethodSignature:signature];
				[inv setTarget:self];
				NSString* url = fsv.venueImageURL;
				[inv setSelector:@selector(loadVenueImage:forView:)];
				[inv setArgument:&url
						 atIndex:2];
				[inv setArgument:&imageView atIndex:3];
				NSInvocationOperation* op = [[NSInvocationOperation alloc] initWithInvocation:inv];
				[queue addOperation:op];
				[op release];
			}
		} else if (ann.annType == AnnotationTypeFoursquareTip) {
			UIImage* pinImage = [UIImage imageNamed:@"blank_girl.png"];
			UIImageView* imageView = [[[UIImageView alloc] initWithImage:pinImage] autorelease];
			imageView.frame = CGRectMake(0, 0, 32, 32);
			CGPoint c = imageView.center;
			c.x = c.x - 9;
			c.y = c.y - 10;
			imageView.center = c;
			[pinView addSubview:imageView];
			pinView.calloutOffset = CGPointMake(-8, -10);
			
			FoursquareTip* fst = ((FoursquareTipAnnotation*)ann).tipInfo;
			
			if ([fst.tipUserImageURL length] > 0) {
				SEL mySel = @selector(loadVenueImage:forView:);
				NSMethodSignature* signature = [[self class] instanceMethodSignatureForSelector:mySel];
				NSInvocation* inv = [NSInvocation invocationWithMethodSignature:signature];
				[inv setTarget:self];
				NSString* url = fst.tipUserImageURL;
				[inv setSelector:@selector(loadVenueImage:forView:)];
				[inv setArgument:&url
						 atIndex:2];
				[inv setArgument:&imageView atIndex:3];
				NSInvocationOperation* op = [[NSInvocationOperation alloc] initWithInvocation:inv];
				[queue addOperation:op];
				[op release];
			}
			
		} else if (ann.annType == AnnotationTypeFoursquareSpecial) {
			UIImage* pinImage = [UIImage imageNamed:@"none.png"];
			UIImageView* imageView = [[[UIImageView alloc] initWithImage:pinImage] autorelease];
			CGPoint c = imageView.center;
			c.x = c.x - 9;
			c.y = c.y - 10;
			imageView.center = c;
			[pinView addSubview:imageView];
			pinView.calloutOffset = CGPointMake(-8, -10);
			
			FoursquareVenue* fsv = ((FoursquareSpecialAnnotation*)ann).venueInfo;
			
			if ([fsv.venueImageURL length] > 0) {
				SEL mySel = @selector(loadVenueImage:forView:);
				NSMethodSignature* signature = [[self class] instanceMethodSignatureForSelector:mySel];
				NSInvocation* inv = [NSInvocation invocationWithMethodSignature:signature];
				[inv setTarget:self];
				NSString* url = fsv.venueImageURL;
				[inv setSelector:@selector(loadVenueImage:forView:)];
				[inv setArgument:&url
						 atIndex:2];
				[inv setArgument:&imageView atIndex:3];
				NSInvocationOperation* op = [[NSInvocationOperation alloc] initWithInvocation:inv];
				[queue addOperation:op];
				[op release];
			}
		} else if (ann.annType == AnnotationTypeFoursquareFriend) {
			UIImage* pinImage = [UIImage imageNamed:@"blank_girl.png"];
			UIImageView* imageView = [[[UIImageView alloc] initWithImage:pinImage] autorelease];
			imageView.frame = CGRectMake(0, 0, 32, 32);
			CGPoint c = imageView.center;
			c.x = c.x - 9;
			c.y = c.y - 10;
			imageView.center = c;
			[pinView addSubview:imageView];
			pinView.calloutOffset = CGPointMake(-8, -10);
			
			FoursquareFriendAnnotation* fsfa = (FoursquareFriendAnnotation*)ann;
			
			if (fsfa.friendUserImageURL && [fsfa.friendUserImageURL length] > 0) {
				SEL mySel = @selector(loadVenueImage:forView:);
				NSMethodSignature* signature = [[self class] instanceMethodSignatureForSelector:mySel];
				NSInvocation* inv = [NSInvocation invocationWithMethodSignature:signature];
				[inv setTarget:self];
				NSString* url = fsfa.friendUserImageURL;
				[inv setSelector:@selector(loadVenueImage:forView:)];
				[inv setArgument:&url
						 atIndex:2];
				[inv setArgument:&imageView atIndex:3];
				NSInvocationOperation* op = [[NSInvocationOperation alloc] initWithInvocation:inv];
				[queue addOperation:op];
				[op release];
			}
			
		} else if (ann.annType == AnnotationTypeTarget) {
			UIImage* pinImage = [UIImage imageNamed:@"target.png"];
			[pinView setImage:pinImage];
			pinView.enabled = NO;
			pinView.centerOffset = CGPointMake(-16, 23);
		}
		
		if (ann.annType == AnnotationTypePinNoteSilent || 
			ann.annType == AnnotationTypeTarget) {
			[pinView setAnimatesDrop:NO];
			ann.annType = AnnotationTypePinNote;
		} else {
			[pinView setAnimatesDrop:YES];
		}
		
		[pinView setCanShowCallout:YES];
		
		if (ann.annType == AnnotationTypePinNoteTemporary || 
			ann.annType == AnnotationTypePOI) {
			[pinView setRightCalloutAccessoryView:nil];
		} else {
			UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
			[rightButton addTarget:self	
							action:@selector(callAnnotationDisclosure) 
				  forControlEvents:UIControlEventTouchUpInside];
			[pinView setRightCalloutAccessoryView:rightButton];
		}
	}
	
	return pinView;
}

/**
 * Helper method to dismiss the accelerometer reminder.
 */
- (void)dismissReminder {
	//reminderEnabled = NO;
	[GraphicsUtilities hidePromptAnimated:YES];
}

/**
 * Helper method to update a Foursquare pin image..
 */
- (void)loadVenueImage:(NSString*)url forView:(UIImageView*)imageView {
	NSMutableData* imageData = [NSMutableData alloc];
	[imageData initWithContentsOfURL:[NSURL URLWithString:url]];
	UIImage* image = [[UIImage alloc] initWithData:imageData];
	
	[imageView setImage:image];
	
	[imageData autorelease];
	[image release];
}

/**
 * Get the view controller on which to overlay the pin note tools.
 *
 * @return  the view controller on which to overlay toolbars
 * @see     PinNoteManagerDelegate
 */
- (UIViewController*)getViewController {
	return self;
}

/**
 * Place a temporary target annotation at the specified location.
 *
 * @param location  the location of target annotation
 * @see             FoursquareManagerDelegate
 */
- (void)displayTargetAtLocation:(CLLocationCoordinate2D)location {
	if (targetAnnotation) {
		[mapView removeAnnotation:targetAnnotation];
		//targetAnnotation = nil;
	}
	
	targetAnnotation = [AddressAnnotation mapAnnotationWithCoordinate:location 
															 andTitle:@"" 
														  andSubtitle:@""
															  andType:AnnotationTypeTarget];
	[mapView addAnnotation:targetAnnotation];
}

/**
 * Clear the temporary target annotation.
 *
 * @see  FoursquareManagerDelegate
 */
- (void)removeTargetAnnotation {
	if (targetAnnotation) {
		[mapView removeAnnotation:targetAnnotation];
		targetAnnotation = nil;
	}
}

/**
 * Add a temporary pin note to the map.
 *
 * @param coord        the coordinates of the pin
 * @param title        the title of the annotation of the pin
 * @param description  the description of the pin
 * @see                PinNoteManagerDelegate
 */
- (void)addPinNoteWithCoord:(CGPoint)coord 
					  title:(NSString*)title 
				description:(NSString*)description {
	AddressAnnotation* addAnn = [AddressAnnotation mapAnnotationWithCoordinate:[mapView convertPoint:coord 
																				toCoordinateFromView:mapView] 
																	  andTitle:title 
																   andSubtitle:description
																   andType:AnnotationTypePinNoteTemporary];
	[pinNotes addObject:addAnn];
	[mapView addAnnotation:addAnn];
}

/**
 * Remove all temporary pin notes on the map.
 *
 * @see  PinNoteManagerDelegate
 */
- (void)removePinNotes {
	[self.mapView removeAnnotations:pinNotes];
	[pinNotes removeAllObjects];
}

/**
 * Save all temporary pin notes on the map.
 *
 * @see  PinNoteManagerDelegate
 */
- (void)savePinNotes {
	for (AddressAnnotation* aa in pinNotes) {
		aa.annType = AnnotationTypePinNote;
		[[AddressAnnotationCollection sharedAddressAnnotationCollection] addDataType:aa];
	}
	
	[pinNotes removeAllObjects];
}

/**
 * Show all persistent pin notes on the map.
 *
 * @see  PinNoteManagerDelegate
 */
- (void)showAllPinNotes {
	NSArray* aaList = [[AddressAnnotationCollection sharedAddressAnnotationCollection] getDataTypes];
	NSArray* anotherAaList = [aaList copyWithZone:NULL]; 
	
	[mapView removeAnnotations:anotherAaList];
	for (AddressAnnotation* aa in anotherAaList) {
		[self showSavedAnnotation:aa];
	}
	
	[self zoomToFitMapAnnotations];
}

/**
 * Save the given updated persistent pin note.
 *
 * @param pinNote  the updated pin note
 * @see            PinNoteManagerDelegate
 */
- (void)savePinNote:(AddressAnnotation*)pinNote {
	[[AddressAnnotationCollection sharedAddressAnnotationCollection] saveData];
	
	//[pinNotes removeAllObjects];
	if (pinNote) {
		[mapView removeAnnotation:pinNote];
		[self showSavedAnnotation:pinNote];
	}
}

/**
 * Delete the given persistent pin note.
 * 
 * @param pinNote  the pin note to delete
 * @see            PinNoteManagerDelegate
 */
- (void)deletePinNote:(AddressAnnotation*)pinNote {
	[[AddressAnnotationCollection sharedAddressAnnotationCollection] removeDataType:pinNote];
	[mapView removeAnnotation:pinNote];
}

/**
 * Relocate the given persistent pin note.
 * 
 * @param pinNote  the pin note to relocate
 * @param coord    the new location for the pin note
 * @see            PinNoteManagerDelegate
 */
- (void)relocatePinNote:(AddressAnnotation*)pinNote 
			  withCoord:(CGPoint)coord {
	AddressAnnotationCollection* aac;
	aac = [AddressAnnotationCollection sharedAddressAnnotationCollection];
	
	[aac removeDataType:pinNote];
	[mapView removeAnnotation:pinNote];
	
	[pinNote setCoordinate:[mapView convertPoint:coord toCoordinateFromView:mapView]];
	// not deleting from the persistent coll
	
	[aac addDataType:pinNote];
	[self showSavedAnnotation:pinNote];
	
	[aac saveData];
}

/**
 * Toggle visibility of the given persistent pin note.
 *
 * @param pinNote  the pin note to show or hide
 * @see            PinNoteManagerDelegate
 */
- (void)toggleVisibilityOfPinNote:(AddressAnnotation*)pinNote {
	for (id ann in self.mapView.annotations) {
		AddressAnnotation* annotation = (AddressAnnotation*)ann;
		
		if (ann != self.mapView.userLocation && 
			annotation.annType == AnnotationTypePinNote) {
			
			if ([annotation isEqual:pinNote]) {
				[mapView removeAnnotation:pinNote];
				return;
			}
		}
	}
	
	[self showSavedAnnotation:pinNote];
}

/**
 * Handle a pin note tools deactivated event.
 * @see  PinNoteManagerDelegate
 */
- (void)pinNoteToolsDeactivated {
	mapTabBar.selectedItem = nil;
}

/**
 * Refresh currently active pin notes.
 * @see  PinNoteManagerDelegate
 */
- (void)refreshPinNotes {
	NSMutableArray* refreshList = [NSMutableArray array];
	
	for (id ann in self.mapView.annotations) {
		AddressAnnotation* annotation = (AddressAnnotation*)ann;
		if (ann != self.mapView.userLocation && 
			annotation.annType == AnnotationTypePinNote) {
			[refreshList addObject:annotation];
		}
	}
	
	for (AddressAnnotation* ann in refreshList) {
		[mapView removeAnnotation:ann];
		[self showSavedAnnotation:ann];
	}
}

/**
 * Deallocate the map view controller.
 */
- (void)dealloc {
	[reachabilityTimer invalidate], reachabilityTimer = nil;
	[reminderTimer invalidate], reminderTimer = nil;
	[queue cancelAllOperations];
	[queue release], queue = nil;
	[pinNotes release], pinNotes = nil;
	[mapView release], mapView = nil;
	[mapTabBar release], mapTabBar = nil;
	[settingsItem release], settingsItem = nil;
	[pinNotesItem release], pinNotesItem = nil;
	[searchItem release], searchItem = nil;
	[bookmarksItem release], bookmarksItem = nil;
	[[BuildingCollection sharedBuildingCollection] dealloc];
    [super dealloc];
}

@end